package com.gitee.wsl.unit.byte

import com.gitee.wsl.ext.any.ifNullValue
import com.gitee.wsl.ext.base.isNot0
import com.gitee.wsl.unit.BaseUnit
import com.gitee.wsl.unit.SubBaseUnit
import com.gitee.wsl.unit.UnitType
import com.gitee.wsl.unit.byte.ByteUnit.Companion.generateBitUnit
import com.gitee.wsl.unit.byte.ByteUnit.Companion.generateByteUnit
import kotlinx.serialization.Serializable

sealed interface ByteUnitType: UnitType<Long> {

    data object B:ByteUnitType {
        override val baseUnit: Long=1
    }

    sealed interface DecimalBitType:ByteUnitType

    sealed interface BinaryByteType:ByteUnitType

    data object KB:DecimalBitType {
        override val baseUnit: Long=1000
    }

    data object KiB:BinaryByteType {
        override val baseUnit: Long=1024
    }

    data object MB:DecimalBitType {
        override val baseUnit: Long = KB.baseUnit * 1000
    }

    data object MiB:BinaryByteType {
        override val baseUnit: Long = KiB.baseUnit * 1024
    }

    data object GB:DecimalBitType {
        override val baseUnit: Long = MB.baseUnit*1000
    }

    data object GiB:BinaryByteType {
        override val baseUnit: Long = MiB.baseUnit*1024
    }

    data object TB:DecimalBitType {
        override val baseUnit: Long = GB.baseUnit*1000
    }

    data object TiB:BinaryByteType {
        override val baseUnit: Long = GiB.baseUnit*1024
    }

    data object PB:DecimalBitType {
        override val baseUnit: Long = TB.baseUnit*1000
    }

    data object PiB:BinaryByteType {
        override val baseUnit: Long = TiB.baseUnit*1024
    }
}

@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") // Our delegate serializer handles subclasses fine
@Serializable(with = DataSizeSerializer::class)
sealed class ByteUnit<SUB: SubBaseUnit<Long, ByteUnitType, *>>(value:Long, type:ByteUnitType, bUnitValue:SUB?=null):SubBaseUnit<Long,ByteUnitType,SUB>(value, type, bUnitValue) {

    override fun toBase(): Long {
        return value * type.baseUnit + bUnitValue.ifNullValue(0,bUnitValue!!.toBase())
    }

    override fun convertTo(desType: ByteUnitType):ByteUnit<*> = convert(toBase(),desType)

    private fun convert(baseByte:Long,desType:ByteUnitType):ByteUnit<*>{
        return when(desType){
            ByteUnitType.B->{
                B(baseByte)
            }
            ByteUnitType.KB->{
                KB(baseByte/ByteUnitType.KB.baseUnit)
            }
            ByteUnitType.MB->{
                MB(baseByte/ByteUnitType.MB.baseUnit)
            }
            ByteUnitType.GB->{
                GB(baseByte/ByteUnitType.GB.baseUnit)
            }
            ByteUnitType.TB->{
                TB(baseByte/ByteUnitType.TB.baseUnit)
            }
            ByteUnitType.PB->{
                PB(baseByte/ByteUnitType.PB.baseUnit)
            }

            ByteUnitType.KiB->{
                KiB(baseByte/ByteUnitType.KiB.baseUnit)
            }
            ByteUnitType.MiB->{
                MiB(baseByte/ByteUnitType.MiB.baseUnit)
            }
            ByteUnitType.GiB->{
                GiB(baseByte/ByteUnitType.GiB.baseUnit)
            }
            ByteUnitType.TiB->{
                TiB(baseByte/ByteUnitType.TiB.baseUnit)
            }
            ByteUnitType.PiB->{
                PiB(baseByte/ByteUnitType.PiB.baseUnit)
            }
        }
    }

    class B(value:Long):ByteUnit<B>(value,ByteUnitType.B){
        override fun format(): String ="$value B"
        override fun toBase(): Long {
            return value
        }
    }

    class KB(value:Long,bUnitValue:B?=null):ByteUnit<B>(value,ByteUnitType.KB,bUnitValue){
        override fun format(): String ="$value KB"

        companion object{
            fun generate(sourceCount: Long):KB{
                val subValue = sourceCount % ByteUnitType.KB.baseUnit
                val baseValue = sourceCount / ByteUnitType.KB.baseUnit
                return KB(baseValue,subValue.isNot0(B(subValue)))
            }
        }
    }

    class KiB(value:Long,bUnitValue:B?=null):ByteUnit<B>(value,ByteUnitType.KiB,bUnitValue){
        override fun format(): String ="$value KiB"

        companion object{
            fun generate(sourceCount: Long):KiB{
                val subValue = sourceCount % ByteUnitType.KiB.baseUnit
                val baseValue = sourceCount / ByteUnitType.KiB.baseUnit
                return KiB(baseValue,subValue.isNot0(B(subValue)))
            }
        }
    }


    class MB(value:Long,bUnitValue:KB?=null):ByteUnit<KB>(value,ByteUnitType.MB,bUnitValue){
        override fun format(): String ="$value MB"

        companion object{
            fun generate(sourceCount: Long):MB{
                val subValue = sourceCount % ByteUnitType.MB.baseUnit
                val baseValue = sourceCount / ByteUnitType.MB.baseUnit
                return MB(baseValue,subValue.isNot0(KB.generate(subValue)))
            }
        }
    }

    class MiB(value:Long,bUnitValue:KiB?=null):ByteUnit<KiB>(value,ByteUnitType.MiB,bUnitValue){
        override fun format(): String ="$value MiB"

        companion object{
            fun generate(sourceCount: Long):MiB{
                val subValue = sourceCount % ByteUnitType.MiB.baseUnit
                val baseValue = sourceCount / ByteUnitType.MiB.baseUnit
                return MiB(baseValue,subValue.isNot0(KiB.generate(subValue)))
            }
        }
    }

    class GB(value:Long,bUnitValue:MB?=null):ByteUnit<MB>(value,ByteUnitType.GB,bUnitValue){
        override fun format(): String ="$value GB"

        companion object{
            fun generate(sourceCount: Long):GB{
                val subValue = sourceCount % ByteUnitType.GB.baseUnit
                val baseValue = sourceCount / ByteUnitType.GB.baseUnit
                return GB(baseValue,subValue.isNot0(MB.generate(subValue)))
            }
        }
    }

    class GiB(value:Long,bUnitValue:MiB?=null):ByteUnit<MiB>(value,ByteUnitType.GiB,bUnitValue){
        override fun format(): String ="$value GiB"

        companion object{
            fun generate(sourceCount: Long):GiB{
                val subValue = sourceCount % ByteUnitType.GiB.baseUnit
                val baseValue = sourceCount / ByteUnitType.GiB.baseUnit
                return GiB(baseValue,subValue.isNot0(MiB.generate(subValue)))
            }
        }
    }

    class TB(value:Long,bUnitValue:GB?=null):ByteUnit<GB>(value,ByteUnitType.GB,bUnitValue){
        override fun format(): String ="$value TB"

        companion object{
            fun generate(sourceCount: Long):TB{
                val subValue = sourceCount % ByteUnitType.TB.baseUnit
                val baseValue = sourceCount / ByteUnitType.TB.baseUnit
                return TB(baseValue,subValue.isNot0(GB.generate(subValue)))
            }
        }
    }

    class TiB(value:Long,bUnitValue:GiB?=null):ByteUnit<GiB>(value,ByteUnitType.GiB,bUnitValue){
        override fun format(): String ="$value TiB"

        companion object{
            fun generate(sourceCount: Long):TiB{
                val subValue = sourceCount % ByteUnitType.TiB.baseUnit
                val baseValue = sourceCount / ByteUnitType.TiB.baseUnit
                return TiB(baseValue,subValue.isNot0(GiB.generate(subValue)))
            }
        }
    }

    class PB(value:Long,bUnitValue:TB?=null):ByteUnit<TB>(value,ByteUnitType.PB,bUnitValue){
        override fun format(): String ="$value TB"

        companion object{
            fun generate(sourceCount: Long):PB{
                val subValue = sourceCount % ByteUnitType.PB.baseUnit
                val baseValue = sourceCount / ByteUnitType.PB.baseUnit
                return PB(baseValue,subValue.isNot0(TB.generate(subValue)))
            }
        }
    }

    class PiB(value:Long,bUnitValue:TiB?=null):ByteUnit<TiB>(value,ByteUnitType.PiB,bUnitValue){
        override fun format(): String ="$value TiB"

        companion object{
            fun generate(sourceCount: Long):PiB{
                val subValue = sourceCount % ByteUnitType.PiB.baseUnit
                val baseValue = sourceCount / ByteUnitType.PiB.baseUnit
                return PiB(baseValue,subValue.isNot0(TiB.generate(subValue)))
            }
        }
    }

    companion object{

        fun generateBitUnit(size: Long):ByteUnit<*>{
            if(size<=0) return B(0)

            val base = ByteUnitType.KB.baseUnit

            val kBSize = size / base
            if (kBSize < 1) {
                return B(size)
            }

            val mBSize = kBSize / base
            if (mBSize < 1) {
                return KB.generate(size)
            }

            val mMSize = mBSize / base
            if (mMSize < 1) {
                return MB.generate(size)
            }

            val mGSize = mMSize / base
            if (mGSize < 1) {
                return GB.generate(size)
            }

            val mTSize = mGSize / base
            if (mTSize < 1) {
                return TB.generate(size)
            }

            return PB.generate(size)
        }

        fun generateByteUnit(size: Long):ByteUnit<*>{
            if(size<=0) return B(0)

            val base = ByteUnitType.KiB.baseUnit

            val kBSize = size / base
            if (kBSize < 1) {
                return B(size)
            }

            val mBSize = kBSize / base
            if (mBSize < 1) {
                return KiB.generate(size)
            }

            val mMSize = mBSize / base
            if (mMSize < 1) {
                return MiB.generate(size)
            }

            val mGSize = mMSize / base
            if (mGSize < 1) {
                return GiB.generate(size)
            }

            val mTSize = mGSize / base
            if (mTSize < 1) {
                return TiB.generate(size)
            }

            return PiB.generate(size)
        }

    }
}

val Number.KB get() = ByteUnit.KB(this.toLong())

val Number.MB get() = ByteUnit.MB(this.toLong())

val Number.GB get() = ByteUnit.GB(this.toLong())

val Number.TB get() = ByteUnit.TB(this.toLong())

val Number.KiB get() = ByteUnit.KiB(this.toLong())

val Number.MiB get() = ByteUnit.MiB(this.toLong())

val Number.GiB get() = ByteUnit.GiB(this.toLong())

val Number.TiB get() = ByteUnit.TiB(this.toLong())

infix operator fun ByteUnit<*>.plus(other: ByteUnit<*>):ByteUnit<*> {
    val new = this.toBase() + other.toBase()
    return generateBitUnit(new)
}

infix operator fun ByteUnit<*>.minus(other: ByteUnit<*>):ByteUnit<*> {
    val new = this.toBase() - other.toBase()
    return generateBitUnit(new)
}

internal val Int.Byte:ByteUnit<*> get() = generateBitUnit(this.toLong())

operator fun ByteUnit<*>.times(other: Number):ByteUnit<*>{
    val new = (this.toBase() * other.toDouble()).toLong()
    return if(this.type is ByteUnitType.DecimalBitType) new.toBitSizeUnit() else new.toByteSizeUnit()
}

operator fun ByteUnit<*>.div(other: Number):ByteUnit<*>{
    val new = (this.toBase() / other.toDouble()).toLong()
    return if(this.type is ByteUnitType.DecimalBitType) new.toBitSizeUnit() else new.toByteSizeUnit()
}

operator fun Number.times(other: ByteUnit<*>): ByteUnit<*> = other.times(this)

fun Long.toBitSizeUnit() = generateBitUnit(this)

fun Long.toByteSizeUnit() = generateByteUnit(this)
